<template>
  <div class="container">
    <div class="canvasBox" ref="box"></div>
    <div class="toolbar" v-if="!readonly">
      <el-radio-group v-model="currentType" @change="onCurrentTypeChange">
        <el-radio-button label="selection">选择</el-radio-button>
        <el-radio-button label="rectangle">□</el-radio-button>
        <el-radio-button label="diamond">◇</el-radio-button>
        <el-radio-button label="triangle">△</el-radio-button>
        <el-radio-button label="circle">○</el-radio-button>
        <el-radio-button label="line">─</el-radio-button>
        <el-radio-button label="arrow">↗</el-radio-button>
        <el-radio-button label="freedraw">✎</el-radio-button>
        <el-radio-button label="text">T</el-radio-button>
        <el-radio-button label="image">图片</el-radio-button>
      </el-radio-group>
    </div>
    <div class="message">
      <div class="id">ID:{{ gjson.$route.query.id }}</div>
    </div>
    <Transition>
      <div class="sidebar" v-show="activeElement || hasSelectedElements">
        <div class="elementStyle">
          <!-- 描边 -->
          <div
            class="styleBlock"
            v-if="
              !['text', 'image'].includes(activeElement?.type) ||
              hasSelectedElements
            "
          >
            <div class="styleBlockTitle">边框颜色</div>
            <div class="styleBlockContent">
              <ColorPicker
                type="stroke"
                :value="activeElement?.style.strokeStyle"
                @change="updateStyle('strokeStyle', $event)"
              ></ColorPicker>
            </div>
          </div>
          <!-- 填充 -->
          <div
            class="styleBlock"
            v-if="
              !['image', 'line', 'arrow', 'freedraw'].includes(
                activeElement?.type
              ) || hasSelectedElements
            "
          >
            <div class="styleBlockTitle">内容颜色</div>
            <div class="styleBlockContent">
              <ColorPicker
                type="fill"
                :value="activeElement?.style.fillStyle"
                @change="updateStyle('fillStyle', $event)"
              ></ColorPicker>
            </div>
          </div>
          <!-- 描边宽度 -->
          <div
            class="styleBlock"
            v-if="
              !['image', 'text'].includes(activeElement?.type) ||
              hasSelectedElements
            "
          >
            <div class="styleBlockTitle">边框宽度</div>
            <div class="styleBlockContent">
              <el-radio-group
                v-model="lineWidth"
                @change="updateStyle('lineWidth', $event)"
              >
                <el-radio-button label="small">
                  <div class="lineWidthItem small">
                    <div class="bar"></div>
                  </div>
                </el-radio-button>
                <el-radio-button label="middle">
                  <div class="lineWidthItem middle">
                    <div class="bar"></div>
                  </div>
                </el-radio-button>
                <el-radio-button label="large">
                  <div class="lineWidthItem large">
                    <div class="bar"></div>
                  </div>
                </el-radio-button>
              </el-radio-group>
            </div>
          </div>
          <!-- 边框样式 -->
          <div
            class="styleBlock"
            v-if="
              !['freedraw', 'image', 'text'].includes(activeElement?.type) ||
              hasSelectedElements
            "
          >
            <div class="styleBlockTitle">边框样式</div>
            <div class="styleBlockContent">
              <el-radio-group
                v-model="lineDash"
                @change="updateStyle('lineDash', $event)"
              >
                <el-radio-button :label="0">
                  <div>实线</div>
                </el-radio-button>
                <el-radio-button :label="5">
                  <div>虚线</div>
                </el-radio-button>
                <el-radio-button :label="2">
                  <div>小圆点</div>
                </el-radio-button>
              </el-radio-group>
            </div>
          </div>
          <!-- 透明度 -->
          <div class="styleBlock">
            <div class="styleBlockTitle">图形透明度</div>
            <div class="styleBlockContent">
              <el-slider
                v-model="globalAlpha"
                :min="0"
                :max="1"
                :step="0.1"
                @change="updateStyle('globalAlpha', $event)"
              />
            </div>
          </div>
          <!-- 角度 -->
          <div class="styleBlock" v-if="!hasSelectedElements">
            <div class="styleBlockTitle">图形角度</div>
            <div class="styleBlockContent">
              <el-slider
                v-model="rotate"
                :min="0"
                :max="360"
                :step="1"
                @input="onRotateChange"
              />
<!--              <el-input-number-->
<!--                style="width: 80px; margin-left: 20px;font-size: 1px"-->
<!--                :controls="false"-->
<!--                v-model="rotate"-->
<!--                :min="0"-->
<!--                :max="360"-->
<!--                @focus="onInputNumberFocus"-->
<!--                @blur="onInputNumberBlur"-->
<!--                @change="onRotateChange"-->
<!--              />-->
            </div>
          </div>
          <!-- 操作 -->
          <div class="styleBlock">
            <div class="styleBlockTitle">操作</div>
            <div class="styleBlockContent">
              <el-button
                type="danger"
                :icon="Delete"
                circle
                @click="deleteElement"
              />
              <el-button
                type="primary"
                :icon="CopyDocument"
                circle
                @click="copyElement"
              />
            </div>
          </div>
        </div>
      </div>
    </Transition>
    <div class="footerLeft" @click.stop v-if="!gjson.$route.query.onlyread">
      <!-- 缩放 -->
      <div class="blockBox">
        <el-tooltip effect="light" content="缩小" placement="top">
          <el-button :icon="Minus" circle @click="zoomOut"  size="small"/>
        </el-tooltip>
        <el-tooltip effect="light" content="重置缩放" placement="top">
          <span class="zoom" @click="resetZoom">{{ currentZoom }}%</span>
        </el-tooltip>
        <el-tooltip effect="light" content="放大" placement="top">
          <el-button :icon="Plus" circle @click="zoomIn"  size="small"/>
        </el-tooltip>
      </div>
      <!-- 前进回退 -->
      <div class="blockBox" v-if="!readonly">
        <el-tooltip effect="light" content="回退" placement="top">
          <el-button
            :icon="Back"
            circle
            :disabled="!canUndo"
            size="small"
            @click="undo"
          />
        </el-tooltip>
        <el-tooltip effect="light" content="前进" placement="top">
          <el-button
            :icon="Right"
            circle
            size="small"
            :disabled="!canRedo"
            @click="redo"
          />
        </el-tooltip>
      </div>
      <!-- 橡皮擦、清空 -->
      <div class="blockBox">
        <!-- 橡皮擦 -->
        <el-tooltip
          effect="light"
          :content="currentType === 'eraser' ? '关闭橡皮擦' : '橡皮擦'"
          placement="top"
        >
          <el-button
            v-if="!readonly"
            :icon="Soccer"
            text
            size="small"
            :type="currentType === 'eraser' ? 'primary' : null"
            @click="toggleEraser"
          />
        </el-tooltip>
        <!-- 只读、编辑模式切换 -->
        <el-tooltip
          effect="light"
          :content="readonly ? '切换到编辑模式' : '切换到只读模式'"
          placement="top"
        >
          <el-button
            :icon="readonly ? View : Edit"
            text
            size="small"
            @click="toggleMode"
          />
        </el-tooltip>
        <!-- 清空 -->
        <el-tooltip effect="light" content="清空" placement="top">
          <el-button v-if="!readonly" :icon="Delete" text @click="empty" size="small"/>
        </el-tooltip>
      </div>
      <!-- 导入导出 -->
      <div class="blockBox">
        <el-tooltip effect="light" content="从json文件导入" placement="top">
          <el-button
            v-if="!readonly"
            :icon="UploadFilled"
           text
            size="small"
            style="margin-right: 10px"
            @click="importFromJson"
          />
        </el-tooltip>
        <el-dropdown @command="handleExportCommand">
          <span class="el-dropdown-link">
            <el-button :icon="Promotion" text size="small"/>
          </span>
          <template #dropdown>
            <el-dropdown-menu>
              <el-dropdown-item command="json">导出json</el-dropdown-item>
            </el-dropdown-menu>
          </template>
        </el-dropdown>
      </div>
<!--      保存-->
      <el-tooltip effect="light" content="保存" placement="top">
        <el-button  :icon="FolderChecked" text @click="Save" size="small"/>
      </el-tooltip>
    </div>
    <!-- 导出图片弹窗 -->
    <el-dialog
      v-model="exportImageDialogVisible"
      title="导出为图片"
      :width="800"
    >
      <div class="exportImageContainer">
        <div class="imagePreviewBox">
          <img :src="exportImageUrl" alt="" />
        </div>
        <div class="handleBox">
          <el-checkbox
            v-model="exportOnlySelected"
            label="仅导出被选中"
            size="large"
            @change="reRenderExportImage"
            style="margin-right: 10px"
          />
          <el-checkbox
            v-model="exportRenderBackground"
            label="背景"
            size="large"
            @change="reRenderExportImage"
            style="margin-right: 10px"
          />
          <el-input
            v-model="exportFileName"
            style="width: 150px; margin-right: 10px"
          ></el-input>
          <el-input-number
            v-model="exportImagePaddingX"
            :min="10"
            :max="100"
            :step="5"
            controls-position="right"
            @change="reRenderExportImage"
            style="margin-right: 10px"
          />
          <el-input-number
            v-model="exportImagePaddingY"
            :min="10"
            :max="100"
            :step="5"
            controls-position="right"
            @change="reRenderExportImage"
            style="margin-right: 10px"
          />
          <el-button type="primary" @click="downloadExportImage"
            >下载</el-button
          >
        </div>
      </div>
    </el-dialog>
    <!-- 导出json弹窗 -->
    <el-dialog
      v-model="exportJsonDialogVisible"
      title="导出为json"
      :width="800"
    >
      <div class="exportJsonContainer">
        <div class="jsonPreviewBox" ref="jsonPreviewBox"></div>
        <div class="handleBox">
          <el-input
            v-model="exportFileName"
            style="width: 150px; margin-right: 10px"
          ></el-input>
          <el-button type="primary" @click="downloadExportJson">下载</el-button>
        </div>
      </div>
    </el-dialog>
    <!-- 帮助弹窗 -->
    <el-dialog v-model="helpDialogVisible" title="帮助" :width="500">
      <div class="helpDialogContent">
        <h2>获取源码</h2>
        <p>
          github：<a
            style="color: #409eff"
            href="https://github.com/wanglin2/tiny_whiteboard"
            target="_blank"
            >https://github.com/wanglin2/tiny_whiteboard</a
          >
        </p>
        <h2>tips</h2>
        <p>移动画布：按住空格键进行拖动</p>
        <h2>快捷键</h2>
        <el-table :data="shortcutKeyList">
          <el-table-column property="name" label="操作" />
          <el-table-column property="value" label="快捷键" />
        </el-table>
      </div>
    </el-dialog>
    <!-- 右键菜单 -->
    <Contextmenu v-if="appInstance" :app="appInstance"></Contextmenu>
  </div>
</template>

<script setup>
import { onMounted, ref, watch,  nextTick, computed, reactive,getCurrentInstance } from 'vue'
import TinyWhiteboard from 'whitepanel'
import ColorPicker from './ColorPicker.vue'
//导入Icon图标
import {
  Delete,
  Plus,
  Minus,
  Back,
  Right,
  Soccer,
  CopyDocument,
  UploadFilled,
  Promotion,
  View,
  Edit,
  FolderChecked,
} from '@element-plus/icons-vue'
import Contextmenu from './Contextmenu.vue'
import axios from 'axios'

// 当前操作类型
const currentType = ref(null)

// dom节点
const box = ref(null)

// 应用实例
let app = null
const appInstance = ref(null)
// 当前激活的元素
const activeElement = ref(null)
// 当前多选的元素
const selectedElements = ref([])
const hasSelectedElements = computed(() => {
  return selectedElements.value.length > 0
})
// 描边宽度
const lineWidth = ref('small')
// 边框样式
const lineDash = ref(0)
// 透明度
const globalAlpha = ref(1)
// 角度
const rotate = ref(0)
// 当前缩放
const currentZoom = ref(100)
// 缩放允许前进后退
const canUndo = ref(false)
const canRedo = ref(false)
// 图片导出弹窗
const exportImageDialogVisible = ref(false)
const exportImageUrl = ref('')
const exportOnlySelected = ref(false)
const exportRenderBackground = ref(true)
const exportFileName = ref('未命名')
const exportImagePaddingX = ref(10)
const exportImagePaddingY = ref(10)
// json导出弹窗
const exportJsonDialogVisible = ref(false)
const exportJsonData = ref('')
const tree = ref(null)
const jsonPreviewBox = ref(null)
// 背景颜色
const backgroundColor = ref('')
// 当前滚动距离
const scroll = reactive({
  x: 0,
  y: 0
})
// 切换显示网格
const showGrid = ref(false)
// 模式切换
const readonly = ref(false)
// 帮助弹窗
const helpDialogVisible = ref(false)
const shortcutKeyList = reactive([
  {
    name: '全部选中',
    value: 'Control + a'
  },
  {
    name: '删除',
    value: 'Del 或 Backspace'
  },
  {
    name: '复制',
    value: 'Control + c'
  },
  {
    name: '粘贴',
    value: 'Control + v'
  },
  {
    name: '放大',
    value: 'Control + +'
  },
  {
    name: '缩小',
    value: 'Control + -'
  },
  {
    name: '重置缩放',
    value: 'Control + 0'
  },
  {
    name: '缩放以适应所有元素',
    value: 'Shift + 1'
  },
  {
    name: '撤销',
    value: 'Control + z'
  },
  {
    name: '重做',
    value: 'Control + y'
  },
  {
    name: '显示隐藏网格',
    value: "Control + '"
  }
])



// 通知app更当前类型,当前操作
watch(currentType, () => {
  app.updateCurrentType(currentType.value)
})

//获取路由及页面
const globaljson=getCurrentInstance();
let gjson=globaljson.appContext.config.globalProperties;


// 元素角度变化
const onElementRotateChange = elementRotate => {
  rotate.value = elementRotate
}

// 修改元素角度
const onRotateChange = rotate => {
  app.updateActiveElementRotate(rotate)
}

// 数字输入框聚焦事件
const onInputNumberFocus = () => {
  // 解绑快捷键按键事件，防止冲突
  app.keyCommand.unBindEvent()
}

// 数字输入框失焦事件
const onInputNumberBlur = () => {
  // 重新绑定快捷键按键事件
  app.keyCommand.bindEvent()
}

// 更新样式
const updateStyle = (key, value) => {
  app.setCurrentElementsStyle({
    [key]: value
  })
}

// 类型变化
const onCurrentTypeChange = () => {
  // 清除激活项
  app.cancelActiveElement()
}

// 删除元素
const deleteElement = () => {
  app.deleteCurrentElements()
}

// 复制元素
const copyElement = () => {
  app.copyPasteCurrentElements()
}

// 放大
const zoomIn = () => {
  app.zoomIn()
}

// 缩小
const zoomOut = () => {
  app.zoomOut()
}

// 恢复初始缩放
const resetZoom = () => {
  app.setZoom(1)
}

// 橡皮擦
const toggleEraser = () => {
  currentType.value = currentType.value === 'eraser' ? 'selection' : 'eraser'
}

// 回退
const undo = () => {
  app.undo()
}

// 前进
const redo = () => {
  app.redo()
}

// 清空
const empty = () => {
  app.empty()
}

// 导入
const importFromJson = () => {
  let el = document.createElement('input')
  el.type = 'file'
  el.accept = 'application/json'
  el.addEventListener('input', () => {
    let reader = new FileReader()
    reader.onload = () => {
      el.value = null
      if (reader.result) {
        app.setData(JSON.parse(reader.result))
      }
    }
    reader.readAsText(el.files[0])
  })
  el.click()
}

// 导出
const handleExportCommand = type => {
  if (type === 'png') {
    exportImageUrl.value = app.exportImage({
      renderBg: exportRenderBackground.value,
      paddingX: exportImagePaddingX.value,
      paddingY: exportImagePaddingY.value,
      onlySelected: exportOnlySelected.value
    })
    exportImageDialogVisible.value = true
  } else if (type === 'json') {
    exportJsonData.value = app.exportJson()
    exportJsonDialogVisible.value = true
    nextTick(() => {
      if (!tree.value) {
        tree.value = jsonTree.create(exportJsonData.value, jsonPreviewBox.value)
      } else {
        tree.value.loadData(exportJsonData.value)
      }
    })
  }
}


// 下载导出的json
const downloadExportJson = () => {
  let str = JSON.stringify(exportJsonData.value, null, 4)
  let blob = new Blob([str])
  TinyWhiteboard.utils.downloadFile(
    URL.createObjectURL(blob),
    exportFileName.value + '.json'
  )
}

// 保存
const Save = () => {
  var that=this;
  exportJsonData.value = app.exportJson()
  let str = JSON.stringify(exportJsonData.value)
  axios
        .put('http://localhost:8080/panel', {
          id: Number(gjson.$route.query.id),
          json: str
        })
        .then(function (response) {
          alert("保存成功！")
        })
        .catch(function (error) {
        })
}

// 滚动至中心
const scrollToCenter = () => {
  app.scrollToCenter()
}


// 模式切换
const toggleMode = () => {
  if (readonly.value) {
    readonly.value = false
    app.setEditMode()
  } else {
    readonly.value = true
    app.setReadonlyMode()
  }
}

// dom元素挂载完成,初始页面渲染
onMounted(() => {
  // 创建实例
  app = new TinyWhiteboard({
    container: box.value,
    drawType: currentType.value
  })
  var storeData='';
  var model='';
  if(gjson.$route.query.json){
   storeData = gjson.$route.query.json;
  }

  if (storeData) {
    storeData = JSON.parse(storeData)
    if(gjson.$route.query.onlyread){
      model=gjson.$route.query.onlyread;
    }else{
      model=storeData.state.readonly;
    }
    currentZoom.value = parseInt(storeData.state.scale * 100)
    scroll.x = parseInt(storeData.state.scrollX)
    scroll.y = parseInt(storeData.state.scrollY)
    showGrid.value = storeData.state.showGrid
    readonly.value = model
    app.setData(storeData)
  }
  // 监听app内部修改类型事件
  app.on('currentTypeChange', type => {
    currentType.value = type
  })
  // 监听元素激活事件
  app.on('activeElementChange', element => {
    if (activeElement.value) {
      activeElement.value.off('elementRotateChange', onElementRotateChange)
    }
    activeElement.value = element
    if (element) {
      let { style, rotate: elementRotate } = element
      lineWidth.value = style.lineWidth
      lineDash.value = style.lineDash
      globalAlpha.value = style.globalAlpha
      rotate.value = elementRotate
      element.on('elementRotateChange', onElementRotateChange)
    }
  })
  // 元素多选变化
  app.on('multiSelectChange', elements => {
    selectedElements.value = elements
  })
  // 缩放变化
  app.on('zoomChange', scale => {
    currentZoom.value = parseInt(scale * 100)
  })
  // 监听前进后退事件
  app.on('shuttle', (index, length) => {
    canUndo.value = index > 0
    canRedo.value = index < length - 1
  })
  // 监听数据变化
  app.on('change', data => {
    showGrid.value = data.state.showGrid
    localStorage.setItem('TINY_WHITEBOARD_DATA', JSON.stringify(data))
  })
  // 监听滚动变化
  app.on('scrollChange', (x, y) => {
    scroll.y = parseInt(y)
    scroll.x = parseInt(x)
  })
  appInstance.value = app
  // 窗口尺寸变化
  let resizeTimer = null
  window.addEventListener('resize', () => {
    clearTimeout(resizeTimer)
    resizeTimer = setTimeout(() => {
      app.resize()
    }, 300)
  })
})


</script>

<style lang="less">
ul,
ol {
  list-style: none;
}
.v-enter-active,
.v-leave-active {
  transition: all 0.5s ease;
}

.v-enter-from,
.v-leave-to {
  opacity: 0;
  transform: translateX(-300px);
}
//el-radio-button修改样式
.el-radio-button__inner { //修改按钮样式
  background: #F7F8FA;
  color: #333;
  width: 40px;
  font-size: 5px;
  text-align: center;
  padding: 5px;
}


</style>
<style lang="less" scoped>
.container {
  position: fixed;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;

  .toolbar {
    position: absolute;
    left: 200px;
    top: 10px;
    z-index: 1;
    display: flex;
    justify-content: center;
  }
  .canvasBox {
    position: absolute;
    left: 50%;
    top: 50%;
    width: 100%;
    height: 100%;
    transform: translate(-50%, -50%);
    background-color: #fff;
  }

  .sidebar {
    position: absolute;
    left: 10px;
    top: 10px;
    width: 250px;
    background-color: #fff;

    .elementStyle {
      padding: 10px;
      box-shadow: 0 1px 4px rgba(0, 0, 0, 0.25);
      border-radius: 4px;

      .styleBlock {
        margin-bottom: 10px;

        .styleBlockTitle {
          color: #343a40;
          font-size: 8px;
          margin-bottom: 2px;
        }

        .styleBlockContent {
          display: flex;

          .lineWidthItem {
            display: flex;
            width: 20px;
            height: 5px;
            align-items: center;

            .bar {
              width: 100%;
              background-color: #212529;
            }

            &.small {
              .bar {
                height: 2px;
              }
            }

            &.middle {
              .bar {
                height: 4px;
              }
            }

            &.large {
              .bar {
                height: 6px;
              }
            }
          }

          /deep/ .el-radio-group {
            .el-radio-button {
              &.is-active {
                .lineWidthItem {
                  .bar {
                    background-color: #fff;
                  }
                }
              }
            }
          }
        }
      }
    }
  }

  .footerLeft {
    position: absolute;
    left: 220px;
    bottom: 10px;
    height: 40px;
    display: flex;
    align-items: center;

    .blockBox {
      height: 100%;
      display: flex;
      align-items: center;
      padding: 0 5px;
      .zoom {
        width: 40px;
        margin: 0 5px;
        user-select: none;
        color: #606266;
        cursor: pointer;
        height: 32px;
        display: flex;
        align-items: center;
        background-color: #fff;
        border-radius: 2px;
        padding: 0 5px;
        justify-content: center;
      }
    }
  }
}

.exportImageContainer {
  .imagePreviewBox {
    height: 400px;
    background: url('')
      0;
    padding: 10px;

    img {
      width: 100%;
      height: 100%;
      object-fit: scale-down;
    }
  }

  .handleBox {
    display: flex;
    align-items: center;
    height: 50px;
    justify-content: center;
  }
}

.exportJsonContainer {
  .jsonPreviewBox {
    height: 400px;
    overflow: auto;
    background-color: #f5f5f5;
    font-size: 14px;
    color: #000;

    /deep/ .jsontree_tree {
      font-family: 'Trebuchet MS', Arial, sans-serif !important;
    }
  }

  .handleBox {
    display: flex;
    align-items: center;
    height: 50px;
    justify-content: center;
  }
}

.helpDialogContent {
  height: 500px;
  overflow: auto;
}

.message{
  position: relative;
  left: 610px;
  top: 5px;
  padding:5px;
  border: 5px solid transparent;
  background: linear-gradient(white,white) padding-box,repeating-linear-gradient(-45deg, red 0, red 12.5%, transparent 0, transparent 25%, #58a 0, #58a 37.5%, transparent 0, transparent 50%) 0/5em 5em;
  font-size: 10px;
}
</style>
